// lab1-1.c för TSBK03
// Modifierad version av lab3-1.c från TSBK07.
// Mål: Rendera med blooming-effekt med hjälp av HDR

// Finns: Shaderladdare. FBO-byggare. Displayfunktion som renderar
// en textur med en animerad tekanna på en likadan tekanna (via FBO).
// Enkel exempelshader.

// Var skall du arbeta?
// I display() applicerar du dina shaders på vald geometri, byter mellan shaders,
// renderar geometri (oftast en enda polygon). Bryt isär efter behag när du får
// kod med upprepningar.
// I init() måste du lägga till laddning av nya shaders.

// Delmål:
// - Skriv en shader för första passet som gör en enkel ljussättning med en
// tydlig högdager >> 1.
// - Skriv en filtershader som renderar sista passet med filter.
// - Rendera till en polygon över hela framebuffern.
// - Med hjälp av nyssnämnda, applicera filter flera pass.
// - Utför HDR genom uppdelning i två intervall, under och över 1. Filtrera det högre intervallet kraftigt, summera ihop.

// Observera att del 2 av labben INTE görs i detta skal. Ett förenklat skal
// finns speciellt för bump mapping.


#include <stdio.h>
#include <math.h>
#include <stdlib.h>

#define GL_GLEXT_PROTOTYPES
#include <GL/glut.h>

#ifndef GL_VERSION_2_0
#error This program needs OpenGL 2.0 libraries to compile
#endif

#include "helpers.h"
#include "shaderutils.h"


Model* model = 0;
int pwnage = 0;
int lastw, lasth; // Behövs för att ställa tillbaka viewport

#define NUM_LIGHTS 4

typedef struct
{
  int enabled;
  GLfloat position[4];
  GLfloat ambient[4];
  GLfloat diffuse[4];
  GLfloat specular[4];
} LightInfo;

LightInfo lights[NUM_LIGHTS] =
  {
    { // Light 0
      1,                      // Disabled/Enabled
      { 0.1, 0.03, 1.0, 0.0 }, // gl_LightSource[0].position
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[0].ambient
      { 0.5, 0.5, 0.5, 0.0 }, // gl_LightSource[0].diffuse
      { 1.0, 1.0, 1.0, 1.0 }, // gl_LightSource[0].specular
    },

    { // Light 1
      0,                      // Disabled/Enabled
      { 1.0, 0.0,-1.0, 1.0 }, // gl_LightSource[1].position
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[1].ambient
      { 0.7, 0.0, 0.0, 0.0 }, // gl_LightSource[1].diffuse
      { 1.0, 1.0, 1.0, 0.0 }, // gl_LightSource[1].specular
    },

    { // Light 2
      0,                      // Disabled/Enabled
      { 1.0, 1.0, 0.0, 1.0 }, // gl_LightSource[2].position
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[2].ambient
      { 0.0, 0.7, 0.0, 0.0 }, // gl_LightSource[2].diffuse
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[2].specular
    },

    { // Light 3
      0,                      // Disabled/Enabled
      { 1.0, 0.0,-3.0, 1.0 }, // gl_LightSource[3].position
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[3].ambient
      { 0.0, 0.0, 0.7, 0.0 }, // gl_LightSource[3].diffuse
      { 0.0, 0.0, 0.0, 0.0 }, // gl_LightSource[3].specular
    },
  };

// *** Skapa shader (ladda, kompilera) ***
// Vi behöver flera shaders så det är trevligt att skapa varje med en rad
GLuint buildShader(char *vertShaderFile, char *fragShaderFile)
{
  // Create vertex shader
  GLuint vertexShader = createShaderFromFile(GL_VERTEX_SHADER, vertShaderFile);
  if (!vertexShader)
    return 0;

  // Create fragment shader
  GLuint fragmentShader = createShaderFromFile(GL_FRAGMENT_SHADER, fragShaderFile);
  if (!fragmentShader)
    return 0;

  // Create shader program
  GLuint shaderProgram = createShaderProgram(vertexShader, fragmentShader);
  if (!shaderProgram)
    return 0;

// Standard uniforms
// Bug fixes here 080918!
  glUseProgram(shaderProgram);
  GLint loc;
  loc = glGetUniformLocation(shaderProgram, "texUnit");
  glUniform1i(loc, 0); // texture unit 0
  loc = glGetUniformLocation(shaderProgram, "texUnit2");
  glUniform1i(loc, 1); // texture unit 1
  glUseProgram(0);
    
  return shaderProgram;
}

void CHECK_FRAMEBUFFER_STATUS()
{
	GLenum status;
	status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	if (status != GL_FRAMEBUFFER_COMPLETE_EXT)
		printf("Framebuffer not complete\n");
}

// En struktur för att samla information om en FBO
typedef struct FBOstruct
{
	GLuint texid;
	GLuint fb;
	GLuint rb;
	int width, height;
} FBOstruct;

// *** Skapa FBO ***
struct FBOstruct *initFBO(int width, int height)
{
	struct FBOstruct *fbo = malloc(sizeof(FBOstruct));
	
	fbo->width = width;
	fbo->height = height;
	
// initialize texture 1
	glGenTextures(1, &fbo->texid);
	glBindTexture(GL_TEXTURE_2D, fbo->texid);
//	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_BYTE, 0L); // NULL = Empty texture
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA16F_ARB, width, height, 0, GL_RGBA, GL_FLOAT, 0L); // NULL = Empty texture
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); // FBO safe
	glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
	
// create objects
	glGenFramebuffersEXT(1, &fbo->fb); // frame buffer id
	glGenRenderbuffersEXT(1, &fbo->rb); // render buffer id
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fbo->fb);
	glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
								GL_TEXTURE_2D, fbo->texid, 0);

// Renderbuffer
// initialize depth renderbuffer
	glBindRenderbufferEXT(GL_RENDERBUFFER_EXT, fbo->rb);
	glRenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH_COMPONENT24, width, height);
// attach renderbuffer to framebuffer depth buffer
	glFramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT, GL_RENDERBUFFER_EXT, fbo->rb);
	
//	glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);
	CHECK_FRAMEBUFFER_STATUS();
	
	printf("Framebuffer object %d\n", fbo->fb);
	glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	return fbo;
}

// Välj indata (texturer) och utdata (FBO)
void useFBO(struct FBOstruct *out, struct FBOstruct *in1, struct FBOstruct *in2)
{
	if (out != 0L)
		glViewport(0, 0, out->width, out->height);
	else
		glViewport(0, 0, lastw, lasth);
	
	if (out != 0L)
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, out->fb);
	else
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
	glActiveTexture(GL_TEXTURE1);
	if (in2 != 0L)
		glBindTexture(GL_TEXTURE_2D, in2->texid);
	else
		glBindTexture(GL_TEXTURE_2D, 0);
	glActiveTexture(GL_TEXTURE0);
	if (in1 != 0L)
		glBindTexture(GL_TEXTURE_2D, in1->texid);
	else
		glBindTexture(GL_TEXTURE_2D, 0);
}


struct FBOstruct *fbo1, *fbo2, *fbo3;
GLuint shaderProgram = 0;
GLuint shaderProgram2 = 0;
GLuint shaderProgram3 = 0;
GLuint shaderProgram4 = 0;
GLuint shaderProgram5 = 0;
int init()
{
  // Place one-time initialization code here

  // load model
  model = loadModel("teapot.obj");
  setModelCenter(model, 0.f, 0.f, 0.f);
  setModelRadius(model, 0.3f);
  shaderProgram4 = buildShader("lab1-1.vs", "lab1-1d.fs");
  shaderProgram2 = buildShader("lab1-1.vs", "lab1-1b.fs");
  shaderProgram = buildShader("lab1-1.vs", "lab1-1.fs");
  shaderProgram3 = buildShader("lab1-1.vs", "lab1-1c.fs");	
  shaderProgram5 = buildShader("lab1-1.vs", "lab1-1b2.fs");

  fbo1 = initFBO(256, 256);
  fbo2 = initFBO(256, 256);
  fbo3 = initFBO(256, 256);
  
  glEnable(GL_TEXTURE_2D);

  return 1;
}

void setLightSources()
{
  // Feed lightsource settings into OpenGL; when writing shader
  //  code, this data can be read from the gl_LightSource[i] structures

  glMatrixMode(GL_MODELVIEW);
  glPushMatrix();

  for (int light = 0; light < NUM_LIGHTS; light++)
    {
      if (lights[light].enabled)
	{
	  // Lightsource is enabled; set parameters using
	  //  the current light-matrix
	  glLoadMatrixd(getLightMatrix());
	  glLightfv(GL_LIGHT0 + light, GL_POSITION, lights[light].position);
	  glLightfv(GL_LIGHT0 + light, GL_AMBIENT, lights[light].ambient);
	  glLightfv(GL_LIGHT0 + light, GL_DIFFUSE, lights[light].diffuse);
	  glLightfv(GL_LIGHT0 + light, GL_SPECULAR, lights[light].specular);
	}
      else
	{
	  // Lightsource is disabled; set all parameters to zero

	  GLfloat zero[4] = { 0.0 };
	  glLoadIdentity();
	  glLightfv(GL_LIGHT0 + light, GL_POSITION, zero);
	  glLightfv(GL_LIGHT0 + light, GL_AMBIENT, zero);
	  glLightfv(GL_LIGHT0 + light, GL_DIFFUSE, zero);
	  glLightfv(GL_LIGHT0 + light, GL_SPECULAR, zero);
	}
    }

  glPopMatrix();
}

void drawModel(Model *model)
{
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_NORMAL_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glVertexPointer(3, GL_FLOAT, 0, model->vertexArray);
  glNormalPointer(GL_FLOAT, 0, model->normalArray);
  glTexCoordPointer(2, GL_FLOAT, 0, model->texCoordArray);
  glDrawElements(GL_TRIANGLES, model->numIndices, GL_UNSIGNED_INT,
		 model->indexArray);
}

void display()
{
  // This function is called whenever it is time to render
  //  a new frame; due to the idle()-function below, this
  //  function will get called several times per second
  
  // rendera till fbo1!
  useFBO(fbo1, 0L, 0L);
  
  // Clear framebuffer & zbuffer
  glClearColor(0.3, 0, 0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  
  // Position lights
  setLightSources();

  // Setup projection matrix
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
  gluPerspective(90, 1, 0.01, 100);
  
  // Setup object matrix
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  glMultMatrixd(getObjectMatrix());
  
  // Enable Z-buffering
  glEnable(GL_DEPTH_TEST);
  
  // Enable backface culling
  glEnable(GL_CULL_FACE);
  glCullFace(GL_BACK);

  // Activate shader program
  glUseProgram(shaderProgram);
  
  // Draw mesh using array-based API
  drawModel(model);

  // Deactivate shader program
  glUseProgram(0);
 
  glFlush();
  useFBO(fbo2, fbo1, 0L);
  glClearColor(0.3, 0, 1.0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // Activate second shader program
  glUseProgram(shaderProgram4);
  glDisable(GL_CULL_FACE);
  glMatrixMode(GL_PROJECTION);
  glLoadIdentity();
 
  glMatrixMode(GL_MODELVIEW);
  glLoadIdentity();
  //drawModel(model);
  glBegin(GL_QUADS);
  glTexCoord2f(0,0);
  glVertex2i(-1,-1);
  glTexCoord2f(0,1);
  glVertex2i(-1,1);
  glTexCoord2f(1,1);
  glVertex2i(1,1);
  glTexCoord2f(1,0);
  glVertex2i(1,-1);
  glEnd();
  int i;
  int j;
  if(pwnage == 1){
    for(j = 0; j < 30; j++){  
      for(i = 0; i < 4; i++){
	glUseProgram(0);
	glFlush();
	if(i == 0 || i == 2){
	  useFBO(fbo3, fbo2, 0L);
	}
	else{
	  useFBO(fbo2, fbo3, 0L);
	}
	glClearColor(0.3, 0, 1.0, 1);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	// Activate second shader program
	if(i < 2){
	  glUseProgram(shaderProgram2);
	}
	else{
	  glUseProgram(shaderProgram5);
	}
	glDisable(GL_CULL_FACE);
	//drawModel(model);
	glBegin(GL_QUADS);
	glTexCoord2f(0,0);
	glVertex2i(-1,-1);
	glTexCoord2f(0,1);
	glVertex2i(-1,1);
	glTexCoord2f(1,1);
	glVertex2i(1,1);
	glTexCoord2f(1,0);
	glVertex2i(1,-1);
	glEnd();
      }
    }
  }
  glUseProgram(0);
  
  glFlush();

   useFBO(0L, fbo1, fbo2);
  glClearColor(0.3, 0, 1.0, 1);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  // Activate second shader program
  glUseProgram(shaderProgram3);
  glDisable(GL_CULL_FACE);
  //drawModel(model);
  glBegin(GL_QUADS);
  glTexCoord2f(0,0);
  glVertex2i(-1,-1);
  glTexCoord2f(0,1);
  glVertex2i(-1,1);
  glTexCoord2f(1,1);
  glVertex2i(1,1);
  glTexCoord2f(1,0);
  glVertex2i(1,-1);
  glEnd();
  

  // Swap front- and backbuffers
  glutSwapBuffers();
}

void reshape(GLsizei w, GLsizei h)
{
	lastw = w;
	lasth = h;
	
	glViewport(0, 0, w, h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glFrustum(-0.5, 0.5, -0.5, 0.5, 0.5, 20); // left, right, bottom, top, near, far
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	gluLookAt(0, 0, 2.5, 0, 0, 0, 0, 1, 0); // camera, look-at-point, up-vector
}

void idle()
{
  // This function is called whenever the computer is idle

  // As soon as the machine is idle, ask GLUT to trigger rendering of a new
  // frame
  glutPostRedisplay();
}

void functionKeys(int key, int mouseX, int mouseY)
{
  // Listen for function-keys, and enable/disable corresponding 
  //  lightsources when they are pressed
  switch (key)
    {
    case GLUT_KEY_F1: lights[0].enabled = 1 - lights[0].enabled; break;
    case GLUT_KEY_F2: lights[1].enabled = 1 - lights[1].enabled; break;
    case GLUT_KEY_F3: lights[2].enabled = 1 - lights[2].enabled; break;
    case GLUT_KEY_F4: lights[3].enabled = 1 - lights[3].enabled; break;
    case GLUT_KEY_F5: pwnage = 1 - pwnage; break;  
    }
}


int main(int argc, char **argv)
{
  glutInit(&argc, argv);

  // Configure GLUT:
  //  - framebuffer with RGB + Alpha values per pixel
  //  - Z-buffer
  //  - two sets of above mentioned buffers, so that
  //    doublebuffering is possible
  //
  // Initial window size 800x800
  glutInitDisplayMode(GLUT_RGBA | GLUT_DEPTH | GLUT_DOUBLE);
  glutInitWindowSize(800, 800);
  glutCreateWindow("FBO lab");

  // Ensure that the machine the program is running on has
  //  new enough OpenGL drivers
  if (!glGetString(GL_SHADING_LANGUAGE_VERSION))
    {
      fprintf(stderr, "Error: Your OpenGL driver does not support OpenGL 2.0 shaders\n");
      return 0;
    }

  // Call various init functions
  initHelperLibrary();
  if (!init())
    return 0;

  // Register our display- and idle-functions with GLUT
  glutDisplayFunc(display);
  glutReshapeFunc(reshape);
  glutIdleFunc(idle);
  glutSpecialFunc(functionKeys);


  	printf("all init done\n");

  // Enter GLUT's main loop; this function will never return
  glutMainLoop();

  return 0;
}

